/*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.forge.roaster.model.impl;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TextElement;
import org.jboss.forge.roaster.model.Annotation;
import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.Method;
import org.jboss.forge.roaster.model.Type;
import org.jboss.forge.roaster.model.Visibility;
import org.jboss.forge.roaster.model.source.AnnotationSource;
import org.jboss.forge.roaster.model.source.FieldSource;
import org.jboss.forge.roaster.model.source.JavaSource;
import org.jboss.forge.roaster.model.source.MethodSource;
import org.jboss.forge.roaster.model.source.ParameterSource;
import org.jboss.forge.roaster.model.source.PropertyHolderSource;
import org.jboss.forge.roaster.model.source.PropertySource;
import org.jboss.forge.roaster.model.util.Assert;
import org.jboss.forge.roaster.model.util.Strings;
/**
* Implementation of PropertySource.
*
* @author mbenson
* @author <a href="ggastald@redhat.com">George Gastaldi</a>
*
* @param <O>
*/
class PropertyImpl<O extends JavaSource<O> & PropertyHolderSource<O>> implements PropertySource<O>
{
private final O origin;
private String name;
PropertyImpl(String name, O origin)
{
super();
this.origin = origin;
this.name = name;
}
@Override
public Object getInternal()
{
return getOrigin().getInternal();
}
@Override
public O getOrigin()
{
return origin;
}
@Override
public String getName()
{
return name == null ? "<missing>" : name;
}
@Override
public Type<O> getType()
{
if (isAccessible())
{
return getAccessor().getReturnType();
}
if (isMutable())
{
return getMutator().getParameters().get(0).getType();
}
if (hasField())
{
return getField().getType();
}
return null;
}
@Override
public boolean hasField()
{
return getField() != null;
}
@Override
public FieldSource<O> getField()
{
final FieldSource<O> field = getOrigin().getField(name);
if (field != null && !field.isStatic())
{
return field;
}
return null;
}
@Override
public boolean isAccessible()
{
return getAccessor() != null;
}
@Override
public boolean isMutable()
{
return getMutator() != null;
}
@Override
public MethodSource<O> getAccessor()
{
for (MethodSource<O> method : getOrigin().getMethods())
{
if (isAccessor(method))
{
return method;
}
}
return null;
}
@Override
public MethodSource<O> getMutator()
{
final Type<O> type;
if (hasField())
{
type = getField().getType();
}
else if (isAccessible())
{
type = getAccessor().getReturnType();
}
else
{
type = null;
}
for (MethodSource<O> method : getOrigin().getMethods())
{
if (isMutator(method))
{
if (type == null
|| Strings.areEqual(type.getQualifiedName(), method.getParameters().get(0).getType()
.getQualifiedName()))
{
return method;
}
}
}
return null;
}
@Override
public MethodSource<O> createAccessor()
{
Assert.isTrue(getAccessor() == null, "Accessor method already exists");
final Type<O> type = getType();
final String accessorName = methodName(type.isType(boolean.class) ? "is" : "get", name);
final MethodSource<O> result = getOrigin().addMethod().setReturnType(typeName())
.setName(accessorName);
if (!getOrigin().isInterface())
{
result.setVisibility(Visibility.PUBLIC);
if (hasField())
{
final String body = String.format("return %s;", getName());
result.setBody(body);
}
}
return result;
}
@Override
public MethodSource<O> createMutator()
{
Assert.isTrue(getMutator() == null, "Mutator method already exists");
final String mutatorName = methodName("set", name);
final String parameters = String.format("%s %s", typeName(), getName());
final MethodSource<O> result = getOrigin().addMethod().setReturnTypeVoid().setName(mutatorName)
.setParameters(parameters);
if (!getOrigin().isInterface())
{
result.setVisibility(Visibility.PUBLIC);
if (hasField())
{
final String body = String.format("this.%1$s = %1$s;", getName());
result.setBody(body);
}
}
return result;
}
@Override
public FieldSource<O> createField()
{
Assert.isFalse(getOrigin().isInterface(), "An interface cannot declare a nonstatic field");
Assert.isTrue(getField() == null, "Field already exists");
final FieldSource<O> result = getOrigin().addField().setVisibility(Visibility.PRIVATE).setType(typeName())
.setName(name);
if (getOrigin().isEnum())
{
result.setFinal(true);
}
if (isAccessible() && !getAccessor().isAbstract())
{
removeAccessor();
createAccessor();
}
if (isMutable() && !getMutator().isAbstract())
{
removeMutator();
createMutator();
}
return result;
}
@Override
public PropertySource<O> setName(final String name)
{
Assert.isFalse(Strings.isBlank(name), "Property name cannot be null/empty/blank");
if (hasField())
{
getField().setName(name);
}
final String oldName = this.name;
final boolean visitDocTags = true;
final ASTVisitor renameVisitor = new ASTVisitor(visitDocTags)
{
@Override
public boolean visit(SimpleName node)
{
if (Strings.areEqual(oldName, node.getIdentifier()))
{
node.setIdentifier(name);
}
return super.visit(node);
}
@Override
public boolean visit(TextElement node)
{
final String text = node.getText();
if (!text.contains(oldName))
{
return super.visit(node);
}
final int matchLength = oldName.length();
final int textLength = text.length();
final StringBuilder buf = new StringBuilder(text.length());
final ParsePosition pos = new ParsePosition(0);
while (pos.getIndex() < textLength)
{
final int index = pos.getIndex();
final char c = text.charAt(index);
if (Character.isJavaIdentifierStart(c))
{
final int next = index + matchLength;
if (next <= textLength && Strings.areEqual(oldName, text.substring(index, next)))
{
buf.append(name);
pos.setIndex(next);
continue;
}
}
buf.append(c);
pos.setIndex(index + 1);
}
node.setText(buf.toString());
return super.visit(node);
}
};
if (isAccessible())
{
final MethodSource<O> accessor = getAccessor();
final String prefix = accessor.getReturnType().isType(boolean.class) ? "is" : "get";
accessor.setName(methodName(prefix, name));
((MethodDeclaration) accessor.getInternal()).accept(renameVisitor);
}
if (isMutable())
{
final MethodSource<O> mutator = getMutator();
mutator.setName(methodName("set", name));
((MethodDeclaration) mutator.getInternal()).accept(renameVisitor);
}
this.name = name;
return this;
}
@Override
public PropertySource<O> setType(Class<?> clazz)
{
return setType(clazz.getName());
}
@Override
public PropertySource<O> setType(String type)
{
final MethodSource<O> accessor = getAccessor();
final MethodSource<O> mutator = getMutator();
final FieldSource<O> field = getField();
if (accessor != null)
{
final Type<O> originalType = accessor.getReturnType();
accessor.setReturnType(type);
if (originalType.isType(boolean.class) || accessor.getReturnType().isType(boolean.class))
{
// potential name change:
final String accessorName = methodName(accessor.getReturnType().isType(boolean.class) ? "is" : "get",
getName());
accessor.setName(accessorName);
}
}
if (mutator != null)
{
for (ParameterSource<O> param : mutator.getParameters())
mutator.removeParameter(param);
mutator.addParameter(type, getName());
}
if (field != null)
{
field.setType(type);
}
return this;
}
@Override
public PropertySource<O> setType(JavaType<?> entity)
{
return setType(entity.getQualifiedName());
}
@Override
public PropertySource<O> setAccessible(boolean accessible)
{
if (isAccessible() != accessible)
{
if (accessible)
{
createAccessor();
}
else
{
removeAccessor();
}
}
return this;
}
@Override
public PropertySource<O> setMutable(boolean mutable)
{
if (isMutable() != mutable)
{
if (mutable)
{
if (hasField())
{
getField().setFinal(false);
}
createMutator();
}
else
{
if (hasField())
{
getField().setFinal(true);
}
removeMutator();
}
}
return this;
}
@Override
public PropertySource<O> removeAccessor()
{
if (isAccessible())
{
getOrigin().removeMethod(getAccessor());
}
return this;
}
@Override
public PropertySource<O> removeMutator()
{
if (isMutable())
{
getOrigin().removeMethod(getMutator());
}
return this;
}
@Override
public PropertySource<O> removeField()
{
if (hasField())
{
getOrigin().removeField(getField());
}
return this;
}
@Override
public String toString()
{
return "Property: " + getName();
}
@Override
public boolean equals(Object obj)
{
if (obj == this)
{
return true;
}
if (!(obj instanceof PropertyImpl<?>))
{
return false;
}
final PropertyImpl<?> other = (PropertyImpl<?>) obj;
return getOrigin() == other.getOrigin() && Strings.areEqual(getName(), other.getName());
}
@Override
public int hashCode()
{
// compatible with Java 6:
return Arrays.hashCode(new Object[] { getOrigin(), getName() });
}
/**
* Helpful method to determine whether this object actually represents a real property.
*/
public boolean isValid()
{
return hasField() || isAccessible() || isMutable();
}
private String typeName()
{
final Type<O> type = getType();
return type == null ? "<missing>" : type.toString();
}
private boolean isAccessor(Method<O, ?> method)
{
if (method.isConstructor())
{
return false;
}
if (method.isReturnTypeVoid())
{
return false;
}
if (method.getParameters().isEmpty())
{
if (method.getReturnType().isType(boolean.class) && Strings.areEqual(method.getName(), methodName("is", name)))
{
return true;
}
return Strings.areEqual(method.getName(), methodName("get", name));
}
return false;
}
private boolean isMutator(Method<O, ?> method)
{
if (method.isConstructor())
{
return false;
}
return method.isReturnTypeVoid() && method.getParameters().size() == 1
&& Strings.areEqual(method.getName(), methodName("set", name));
}
private static String methodName(String prefix, String property)
{
return prefix + Strings.capitalize(property);
}
@Override
public Annotation<O> getAnnotation(Class<? extends java.lang.annotation.Annotation> type)
{
Annotation<O> ann = null;
FieldSource<O> field = getField();
if (field != null)
{
ann = field.getAnnotation(type);
}
if (ann == null)
{
MethodSource<O> accessor = getAccessor();
if (accessor != null)
{
ann = accessor.getAnnotation(type);
}
}
if (ann == null)
{
MethodSource<O> mutator = getMutator();
if (mutator != null)
{
ann = mutator.getAnnotation(type);
}
}
return ann;
}
@Override
public Annotation<O> getAnnotation(String type)
{
Annotation<O> ann = null;
FieldSource<O> field = getField();
if (field != null)
{
ann = field.getAnnotation(type);
}
if (ann == null)
{
MethodSource<O> accessor = getAccessor();
if (accessor != null)
{
ann = accessor.getAnnotation(type);
}
}
if (ann == null)
{
MethodSource<O> mutator = getMutator();
if (mutator != null)
{
ann = mutator.getAnnotation(type);
}
}
return ann;
}
@Override
public List<? extends Annotation<O>> getAnnotations()
{
List<Annotation<O>> annotations = new ArrayList<Annotation<O>>();
FieldSource<O> field = getField();
if (field != null)
{
List<AnnotationSource<O>> fieldAnnotations = field.getAnnotations();
annotations.addAll(fieldAnnotations);
}
MethodSource<O> accessor = getAccessor();
if (accessor != null)
{
List<AnnotationSource<O>> accessorAnnotations = accessor.getAnnotations();
annotations.addAll(accessorAnnotations);
}
MethodSource<O> mutator = getMutator();
if (mutator != null)
{
List<AnnotationSource<O>> mutatorAnnotations = mutator.getAnnotations();
annotations.addAll(mutatorAnnotations);
}
return annotations;
}
@Override
public boolean hasAnnotation(Class<? extends java.lang.annotation.Annotation> type)
{
boolean hasAnnotation = false;
FieldSource<O> field = getField();
if (field != null)
{
hasAnnotation = field.hasAnnotation(type);
}
if (!hasAnnotation)
{
MethodSource<O> accessor = getAccessor();
if (accessor != null)
{
hasAnnotation = accessor.hasAnnotation(type);
}
}
if (!hasAnnotation)
{
MethodSource<O> mutator = getMutator();
if (mutator != null)
{
hasAnnotation = mutator.hasAnnotation(type);
}
}
return hasAnnotation;
}
@Override
public boolean hasAnnotation(String type)
{
boolean hasAnnotation = false;
FieldSource<O> field = getField();
if (field != null)
{
hasAnnotation = field.hasAnnotation(type);
}
if (!hasAnnotation)
{
MethodSource<O> accessor = getAccessor();
if (accessor != null)
{
hasAnnotation = accessor.hasAnnotation(type);
}
}
if (!hasAnnotation)
{
MethodSource<O> mutator = getMutator();
if (mutator != null)
{
hasAnnotation = mutator.hasAnnotation(type);
}
}
return hasAnnotation;
}
}